Skip to content

Improve paywall loading performance and fix concurrency issues - #453

Merged
ianrumac merged 9 commits into
developfrom
claude/sdk-paywall-performance-plr321
Aug 17, 2026
Merged

Improve paywall loading performance and fix concurrency issues#453
ianrumac merged 9 commits into
developfrom
claude/sdk-paywall-performance-plr321

Conversation

@ianrumac

Copy link
Copy Markdown
Collaborator

Changes in this pull request

This PR significantly improves paywall loading performance and fixes several concurrency-related issues:

Performance Enhancements

  • Concurrent configuration fetch and identity setup: configure() now runs config fetching and identity setup concurrently instead of sequentially, reducing startup time
  • Immediate config availability: Remote configuration is now available to presentations as soon as it's fetched, rather than being gated on Play Billing product prefetching and enrichment
  • Concurrent entitlements and config waits: waitForEntitlementsAndConfig now waits for both entitlements and config concurrently, with proper timeout handling prioritizing entitlements failures
  • Optimized event data persistence: Event data inserts now use a dedicated coroutine lane to prevent blocking occurrence writes

Concurrency and Thread Safety Fixes

  • PaywallRequestManager: Converted from single-threaded actor pattern to multi-threaded IO dispatcher with thread-safe collections:

    • Replaced MutableMap with ConcurrentHashMap for activeTasks and paywallsByHash
    • Added AtomicLong for cache generation tracking to prevent stale writes
    • Introduced dedicated single-lane trackScope for lifecycle event tracking to maintain ordering
    • Fixed race conditions in task deduplication using atomic putIfAbsent and remove(key, value)
  • AutomaticPurchaseController: Improved connection state management:

    • Replaced boolean isConnected with tri-state ConnectionState enum (Connecting/Connected/Failed)
    • Allows waiters to short-circuit on known failures instead of blocking until timeout
    • Added CONNECTION_TIMEOUT_MS constant for explicit timeout handling
  • GoogleBillingWrapper: Enhanced concurrent query handling with atomic operations and proper timeout management

  • PaywallViewCache: Removed blocking runBlocking calls and single-threaded dispatcher:

    • Made _activePaywallVcKey volatile for safe concurrent access
    • Simplified synchronous property access patterns
  • CoreDataManager: Separated event data persistence into dedicated coroutine lane to prevent blocking other database operations

Bug Fixes

  • DefaultWebviewClient: Added page-level failure detection for main frame and runtime bundle errors with volatile state tracking
  • WaitForSubsStatusAndConfig: Fixed timeout handling to properly surface entitlements timeout when both waits fail
  • PaywallView: Added delayed hide logic for loading/shimmer views with proper cancellation on state changes
  • Logger: Optimized logging to avoid building messages when no delegate or log output is configured
  • SWWebView: Added load retry mechanism with MAX_LOAD_RETRIES constant

API Changes

  • ExpressionEvaluating: Added overload for evaluateExpression() that accepts sharedAttributes parameter for CEL evaluation
  • SuperscriptEvaluator: Implemented shared attributes overlay for computed properties
  • PaywallPreload: Added invalidatePreloadFingerprint() method to clear cache on config refresh
  • PaywallManager: Added resetPaywallRequestCache(identifiers) for selective cache invalidation

Testing

  • Added comprehensive tests for concurrent entitlements/config waits
  • Added tests for cache invalidation and selective paywall removal
  • Added SuperscriptEvaluatorTest for computed property overlaying
  • Updated existing tests to use new API signatures and verify concurrent behavior

Checklist

  • All unit tests pass
  • All UI tests pass
  • Demo project builds and runs
  • Added/updated tests for concurrency fixes and performance improvements
  • Updated CHANGELOG.md with enhancements and bug fixes
  • Code follows project conventions and patterns
  • Reviewed contributing guide compliance

https://claude.ai/code/session_01MCNtq6WEfD6su3SjFuhfHV

claude and others added 5 commits August 11, 2026 17:32
…nd rule evaluation

- Publish config as Retrieved immediately after ApplyConfig; run product
  warm-up and enrichment resolution in the background instead of gating
  every presentation on them
- Run configure()'s config fetch and identity setup concurrently
- Fast-path implicit triggers that map to no placement so app_launch/
  session_start no longer head-of-line block register() on the serial queue
- Await entitlements and config concurrently in the presentation pipeline
- Sync subscription status without gating local Play queries on config;
  await billing connection instead of fixed retry sleeps; query SUBS and
  INAPP purchases and product details concurrently
- Make PaywallRequestManager dedup thread-safe (ConcurrentHashMap with
  atomic in-flight claim) and fire lifecycle analytics without blocking
  the load path
- Diff config refreshes by paywall cacheKey and evict only changed
  paywalls from the response cache; invalidate the preload fingerprint on
  refresh changes and fresh assignments so re-preload actually runs
- Make PaywallViewCache synchronous (removes fire-and-forget writes and
  runBlocking reads on a multi-threaded dispatcher)
- Only treat main-frame webview errors as page failures; subresource
  failures no longer restart the page or count toward fallback attempts;
  bounded main-frame retry now works on API 26+
- Animate shimmer via view alpha on a hardware layer instead of
  re-rasterizing a software-layer vector every frame
- Use the fixed default duration for the popup entrance animation and
  apply presentation.delay only to the initial reveal, not the
  post-purchase spinner
- Build the rule-evaluation base context once per evaluation pass while
  resolving computed properties fresh per rule
- Count trigger rule occurrences with SQL COUNT instead of materializing
  rows; give event-data inserts their own serial lane
- Construct log strings lazily on the paywall hot path

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MCNtq6WEfD6su3SjFuhfHV
- Serialize lifecycle analytics on a single lane so start/complete event
  pairs keep their order; guard against zombie fetches from cancelled
  callers; close the double-fetch window in saveRequestHash; skip stale
  cache writes when the request cache was invalidated mid-flight
- Bound-await enrichment (1s) before publishing config as Retrieved so
  fresh installs don't evaluate audience filters without enrichment
- Await a valid config before starting the 30s assignments timeout so a
  slow config fetch can't permanently skip the initial assignment restore
- Retry the config wait once after entitlements resolve so the concurrent
  waits never tolerate less config latency than the old sequential path
- Track billing connection as Connecting/Connected/Failed so restores
  fail fast when billing can never connect; defer Inactive status until
  config has been applied or failed so test mode is respected
- Record main-frame webview errors synchronously so the load retry cap
  can't be reset by the failed load's own onPageFinished; treat failures
  of the paywall runtime bundle as page-level again so fallback URLs
  still engage; report the failing resource's URL in resource-load events
- Build shared rule-evaluation attributes lazily and contain their
  failures so attribute errors degrade to no-match instead of aborting
  the presentation

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MCNtq6WEfD6su3SjFuhfHV
@ianrumac
ianrumac merged commit 9806ce1 into develop Aug 17, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants